namespace Jsl.FxCop
{
    using System;
    using System.Reflection;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Base class for all my rules.
    /// </summary>
    /// <remarks>
    /// This class provides more overrides of the Check method to make writing rules a little easier.
    /// </remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Jsl", Justification = "My initials are spelled correctly!?!")]
    public abstract class JslBaseIntrospectionRule : BaseIntrospectionRule
    {
        private readonly bool checkEvent;
        private readonly bool checkField;
        private readonly bool checkInstruction;
        private readonly bool checkProperty;
        private readonly bool checkMethod;
        private readonly bool checkMethodCall;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="JslBaseIntrospectionRule"/> class.
        /// </summary>
        /// <param name="ruleType">Type of the rule.</param>
        protected JslBaseIntrospectionRule(Type ruleType)
            : this(ruleType, "Jsl.FxCop.Rules")
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JslBaseIntrospectionRule"/> class.
        /// </summary>
        /// <param name="ruleType">Type of the rule.</param>
        /// <param name="resourceName">Name of the resource.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "The parameters are already checked.")]
        protected JslBaseIntrospectionRule(Type ruleType, string resourceName)
            : base(ruleType.Name, resourceName, ruleType.Assembly)
        {
            this.checkEvent = this.IsOverridden(typeof(EventNode));
            this.checkField = this.IsOverridden(typeof(Field));
            this.checkInstruction = this.IsOverridden(typeof(Method), typeof(int), typeof(Instruction));
            this.checkProperty = this.IsOverridden(typeof(PropertyNode));
            this.checkMethod = this.IsOverridden(typeof(Method));
            this.checkMethodCall = this.IsOverridden(typeof(Method), typeof(int), typeof(Method));
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The problems found if any.</returns>
        public override ProblemCollection Check(Member member)
        {
            // Check if we are processing methods or stuff inside methods.
            if (this.checkMethod || this.checkInstruction || this.checkMethodCall)
            {
                Method method = member as Method;
                if (method != null)
                {
                    // Only process methods that match the filter.
                    if (this.FilterMethod(method))
                    {
                        if (this.checkMethod)
                        {
                            this.Check(method);
                        }
                        else
                        {
                            this.CheckInsideMethod(method);
                        }
                    }
                }
            }

            if (this.checkEvent)
            {
                EventNode eventNode = member as EventNode;
                if (eventNode != null)
                {
                    this.Check(eventNode);
                }
            }

            if (this.checkField)
            {
                Field field = member as Field;
                if (field != null)
                {
                    this.Check(field);
                }
            }

            if (this.checkProperty)
            {
                PropertyNode property = member as PropertyNode;
                if (property != null)
                {
                    this.Check(property);
                }
            }

            return this.Problems;
        }

        /// <summary>
        /// Checks the specified event.
        /// </summary>
        /// <param name="eventNode">The event.</param>
        /// <returns>The problems found if any.</returns>
        public virtual ProblemCollection Check(EventNode eventNode)
        {
            return this.Problems;
        }

        /// <summary>
        /// Checks the specified field.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <returns>The problems found if any.</returns>
        public virtual ProblemCollection Check(Field field)
        {
            return this.Problems;
        }

        /// <summary>
        /// Checks the specified method.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <returns>The problems found if any.</returns>
        public virtual ProblemCollection Check(Method method)
        {
            return this.CheckInsideMethod(method);
        }

        /// <summary>
        /// Checks each instruction of the method.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="instructionIndex">Index of the instruction.</param>
        /// <param name="instruction">The instruction.</param>
        /// <returns>The problems found if any.</returns>
        public virtual ProblemCollection Check(Method method, int instructionIndex, Instruction instruction)
        {
            return this.Problems;
        }

        /// <summary>
        /// Checks each method call in the containing method.
        /// </summary>
        /// <param name="method">The method.</param>
        /// <param name="instructionIndex">Index of the instruction.</param>
        /// <param name="calledMethod">The called method.</param>
        /// <returns>The problems found if any.</returns>
        public virtual ProblemCollection Check(Method method, int instructionIndex, Method calledMethod)
        {
            return this.Problems;
        }

        /// <summary>
        /// Checks the specified property.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns>The problems found if any.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Property", Justification = "It is a property...")]
        public virtual ProblemCollection Check(PropertyNode property)
        {
            return this.Problems;
        }

        /// <summary>
        /// Filters the called method.
        /// </summary>
        /// <param name="calledMethod">The called method.</param>
        /// <returns><c>true</c> if the method call should be processed.</returns>
        /// <remarks>
        /// This is used to filter calls to <see cref="Check(Method, int, Method)"/>.
        /// </remarks>
        public virtual bool FilterCalledMethod(Method calledMethod)
        {
            return true;
        }

        /// <summary>
        /// Determines whether this method should be processed or not.
        /// </summary>
        /// <param name="method">The method to check.</param>
        /// <returns><c>true</c> if the method should be processed.</returns>
        public virtual bool FilterMethod(Method method)
        {
            return true;
        }
        #endregion Public Methods

        #region Private Methods
        private ProblemCollection CheckInsideMethod(Method method)
        {
            if (!method.IsAbstract)
            {
                if (this.checkInstruction || this.checkMethodCall)
                {
                    InstructionCollection instructions = method.Instructions;
                    for (int instructionIndex = 0; instructionIndex < instructions.Count; instructionIndex++)
                    {
                        Instruction instruction = instructions[instructionIndex];

                        if (this.checkInstruction)
                        {
                            this.Check(method, instructionIndex, instruction);
                        }

                        if (this.checkMethodCall && InstructionHelper.IsMethodCall(instruction.OpCode))
                        {
                            Method calledMethod = (Method)instruction.Value;
                            if (this.FilterCalledMethod(calledMethod))
                            {
                                this.Check(method, instructionIndex, calledMethod);
                            }
                        }
                    }
                }
            }

            return this.Problems;
        }

        /// <summary>
        /// Determines if the specified Check method is overridden.
        /// </summary>
        /// <param name="parameterTypes">The <see cref="Type"/>s of each parameter.</param>
        /// <returns><c>true</c> if the method is overridden.</returns>
        private bool IsOverridden(params Type[] parameterTypes)
        {
            MethodInfo method = this.GetType().GetMethod("Check", parameterTypes);
            return method.DeclaringType != typeof(JslBaseIntrospectionRule);
        }
        #endregion Private Methods
    }
}